package edu.udo.sopra10.chaturaji.gui.util;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;

/**
 * Simple LayoutManager that holds exactly one component and tries to maximize
 * that component's size while maintaining its aspect ratio.
 * 
 * @author Patrick Westerhoff
 */
public class ProportionalLayout implements LayoutManager
{
	/**
	 * Resize behavior of the container.
	 * 
	 */
	public enum ResizeBehavior
	{
		NONE, HORIZONTAL, VERTICAL, BOTH;
	}
	
	private boolean hasComponent;
	private double defaultRatio;
	private double ratio;
	private ResizeBehavior behavior;
	
	/**
	 * Constructs an empty ProportionalLayout.
	 * 
	 */
	public ProportionalLayout ()
	{
		this( -1, ResizeBehavior.NONE );
	}
	
	/**
	 * Constructs an empty ProportionalLayout with a preset ratio.
	 * 
	 * @param ratio the component's aspect ratio to be maintained.
	 */
	public ProportionalLayout ( double ratio )
	{
		this( ratio, ResizeBehavior.NONE );
	}
	
	/**
	 * Constructs an empty ProportionalLayout with a resize behavior that
	 * decides if the container will be resized after the component has been
	 * laid out.
	 * 
	 * @param behavior the resize behavior to apply.
	 */
	public ProportionalLayout ( ResizeBehavior behavior )
	{
		this( -1, behavior );
	}
	
	/**
	 * Constructs an empty ProportionalLayout with a preset ratio and a resize
	 * behavior that decides if the container will be resized after the
	 * component has been laid out.
	 * 
	 * @param ratio the component's aspect ratio to be maintained.
	 * @param behavior the resize behavior to apply.
	 */
	public ProportionalLayout ( double ratio, ResizeBehavior behavior )
	{
		this.hasComponent = false;
		this.behavior = behavior;
		this.defaultRatio = ratio;
		this.ratio = -1;
	}
	
	@Override
	public void addLayoutComponent ( String name, Component comp )
	{
		if ( this.hasComponent )
			throw new IllegalArgumentException( "Only one component allowed" );
		
		this.hasComponent = true;
	}
	
	@Override
	public void removeLayoutComponent ( Component comp )
	{
		this.hasComponent = false;
		this.ratio = -1;
	}
	
	/**
	 * Calculates the ratio or uses the preset default aspect ratio.
	 * 
	 * @param component the component used to calculate the value.
	 */
	private void calculateRatio ( Component component )
	{
		if ( ratio != -1 )
			return;
		
		if ( defaultRatio != -1 )
			ratio = defaultRatio;
		else
		{
			Dimension size = component.getPreferredSize();
			ratio = size.width / size.height;
		}
	}
	
	@Override
	public void layoutContainer ( Container parent )
	{
		if ( parent.getComponentCount() != 1 )
			return;
		
		Component c = parent.getComponent( 0 );
		Insets insets = parent.getInsets();
		
		// calculate ratio if not already set
		calculateRatio( c );
		
		// available width and height
		int maxWidth = parent.getWidth() - insets.left - insets.right;
		int maxHeight = parent.getHeight() - insets.top - insets.bottom;
		
		// calculate optimal boundaries
		Rectangle bounds = new Rectangle();
		bounds.width = (int) Math.min( maxHeight * ratio, maxWidth );
		bounds.height = (int) ( bounds.width / ratio );
		bounds.x = ( maxWidth - bounds.width ) / 2;
		bounds.y = ( maxHeight - bounds.height ) / 2;
		
		c.setBounds( bounds );
		c.setPreferredSize( bounds.getSize() );
		

		// apply parent container's resize behavior
		switch ( behavior )
		{
			case HORIZONTAL:
				parent.setSize( bounds.width, parent.getSize().height );
				bounds.x = 0;
				break;
			
			case VERTICAL:
				parent.setSize( parent.getSize().width, bounds.height );
				bounds.y = 0;
				break;
			
			case BOTH:
				parent.setSize( bounds.width, bounds.height );
				bounds.x = bounds.y = 0;
				break;
		}
	}
	
	@Override
	public Dimension minimumLayoutSize ( Container parent )
	{
		return layoutSize( parent, false );
	}
	
	@Override
	public Dimension preferredLayoutSize ( Container parent )
	{
		return layoutSize( parent, true );
	}
	
	/**
	 * Calculates the dimensions needed to layout the visible components
	 * contained in the specified target container.
	 * 
	 * @param parent the container that needs to be laid out.
	 * @param usePreferredSize if the preferred size should be calculated.
	 * @return the dimensions of the container.
	 */
	private Dimension layoutSize ( Container parent, boolean usePreferredSize )
	{
		synchronized ( parent.getTreeLock() )
		{
			Component c = parent.getComponent( 0 );
			Dimension size = new Dimension( usePreferredSize ? c.getPreferredSize() : c.getMinimumSize() );
			
			// add container insets
			Insets insets = parent.getInsets();
			size.width += insets.left + insets.right;
			size.height += insets.top + insets.bottom;
			
			return size;
		}
	}
}